﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Net;
using System.Threading;
using System.Windows.Forms;
using GnomoriaBackup.Properties;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace GnomoriaBackup
{
    public partial class Form1 : Form
    {
        private const int CheckForCancel = 100;
        private readonly string _gnomoriaExe = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\Gnomoria\\Gnomoria.exe";
        private readonly string _gnomoriaLogFile = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\My Games\\Gnomoria\\Gnomoria.log";
        private readonly string _gnomoriaWorlds = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\My Games\\Gnomoria\\Worlds";

        private readonly string _appTempLogFile = Path.GetDirectoryName(Application.ExecutablePath) + "\\tempLog.txt";
        private readonly string _appLogPath = Path.GetDirectoryName(Application.ExecutablePath) + "\\log.txt";

        public Form1()
        {
            InitializeComponent();

            //Fill form with saved user data
            if (!string.IsNullOrEmpty(Settings.Default.backupNum))
                txtBackupNum.Text = Settings.Default.backupNum;

            if (!string.IsNullOrEmpty(Settings.Default.backupInterval))
                txtBackupInterval.Text = Settings.Default.backupInterval;

            if (!string.IsNullOrEmpty(Settings.Default.backupWorldPath))
                txtBackupWorldPath.Text = Settings.Default.backupWorldPath;

            if (!string.IsNullOrEmpty(Settings.Default.backupName))
                txtBackupName.Text = Settings.Default.backupName;

            ApiAddComputer();
        }

        private void Form1FormClosing(object sender, FormClosingEventArgs e)
        {
            backupThread.CancelAsync();
        }

        private void BackupThreadDoWork(object sender, DoWorkEventArgs e)
        {
            var threadInfo = e.Argument as ThreadInfo;
            if (threadInfo == null)
                return;

            var backupFile = new FileInfo(Settings.Default.backupWorldPath);

            //if directory doesn't exist create it
            if (!Directory.Exists(backupFile.DirectoryName + "\\Backup"))
                Directory.CreateDirectory(backupFile.DirectoryName + "\\Backup");

            while (backupThread.CancellationPending != true)
            {
                backupThread.ReportProgress(0, "Backing Up File");

                //get all files, order and filter
                var filteredAndOrderedList = Directory.GetFiles(backupFile.DirectoryName + "\\Backup")
                    .Select(backup => new FileInfo(backup))
                    .Where(fileInfo => fileInfo.Extension == ".sav")
                    .OrderBy(x => x.CreationTime)
                    .ToList();

                //delete oldest
                if (filteredAndOrderedList.Count > int.Parse(txtBackupNum.Text))
                    File.Delete(filteredAndOrderedList[0].FullName);

                //try to backup file
                try
                {
                    File.Copy(backupFile.FullName, backupFile.DirectoryName + "\\Backup\\" + txtBackupName.Text + GetTimestamp() + backupFile.Extension);
                }
                catch (Exception ex)
                {
                    backupThread.ReportProgress(0, ex.Message);
                }

                backupThread.ReportProgress(0, "Backup Done");

                decimal totalTimeToWait = threadInfo.BackupInterval * 100 * 60;
                decimal timeWaited = 0;
                decimal timeTillNext = (totalTimeToWait - timeWaited) / 100 / 60;

                if (timeTillNext > 1)
                    backupThread.ReportProgress(0, "Next Backup in " + timeTillNext + " minutes");
                else
                    backupThread.ReportProgress(0, "Next Backup in " + timeTillNext + " minute");

                while (timeWaited < totalTimeToWait)
                {
                    if (backupThread.CancellationPending)
                        return;

                    Thread.Sleep(CheckForCancel);
                    timeWaited += CheckForCancel;
                }
            }
        }

        private void BackupThreadRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            btnStart.Enabled = true;
            btnStop.Enabled = false;

            Log("Backup Stopped");
        }

        private void BackupThreadProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.UserState != null)
                Log(e.UserState as string);
        }

        private void BtnStartClick(object sender, EventArgs e)
        {
            btnStart.Enabled = false;
            btnStop.Enabled = true;
            lstMessages.Items.Clear();

            Settings.Default.backupNum = txtBackupNum.Text;
            Settings.Default.backupInterval = txtBackupInterval.Text;
            Settings.Default.backupWorldPath = txtBackupWorldPath.Text;
            Settings.Default.backupName = txtBackupName.Text;
            Settings.Default.Save();

            backupThread.RunWorkerAsync(new ThreadInfo(int.Parse(txtBackupNum.Text), decimal.Parse(txtBackupInterval.Text)));
        }

        private void BtnStopClick(object sender, EventArgs e)
        {
            backupThread.CancelAsync();
        }

        private void Log(string message)
        {

            using (var file = new StreamWriter(_appLogPath, true))
            {
                file.WriteLine(GetTimestamp() + "_" + message);
            }

            if (lstMessages.Items.Count > 10)
                lstMessages.Items.RemoveAt(0);

            lstMessages.Items.Add(message);
        }

        private void Log(string message, Exception ex)
        {
            Log(message + ex.Message);
            Console.WriteLine(ex.StackTrace);
        }

        public static String GetTimestamp()
        {

            return DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
        }

        private void BtnBrowseClick(object sender, EventArgs e)
        {
            var dlg = new OpenFileDialog
                          {
                              DefaultExt = ".sav",
                              InitialDirectory = _gnomoriaWorlds
                          };

            if (dlg.ShowDialog() != DialogResult.OK) return;

            txtBackupWorldPath.Text = dlg.FileName;
            Settings.Default.backupWorldPath = dlg.FileName;
        }

        private void BtnOpenClientClick(object sender, EventArgs e)
        {
            processThread.RunWorkerAsync();
        }

        private void ProcessThreadDoWork(object sender, DoWorkEventArgs e)
        {
            while (!processThread.CancellationPending)
            {
                ProcessStart();
            }
        }

        private void ProcessStart()
        {
            if (string.IsNullOrEmpty(_gnomoriaExe))
                return;

            var startInfo = new ProcessStartInfo
            {
                WorkingDirectory = Path.GetDirectoryName(_gnomoriaExe),
                FileName = _gnomoriaExe
            };

            try
            {
                using (var exeProcess = Process.Start(startInfo))
                {
                    exeProcess.WaitForExit();
                    processThread.ReportProgress(0, "Process Closed by User");
                    processThread.CancelAsync();
                }
            }
            catch (Exception ex)
            {
                processThread.ReportProgress(0, ex.Message);
                processThread.CancelAsync();
            }
        }

        private void ProcessThreadRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Log("No longer watching process");
        }

        private void ProcessThreadProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Log(e.UserState as string);
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var about = new About();
            about.Show();
        }

        private void SendCrashLog()
        {
            if (string.IsNullOrEmpty(Settings.Default.hash))
                return;

            var crashLog = ReadCrashLog();
            var uri = new Uri("http://ge.gnomoriawiki.com/api/crash/upload");

            try
            {
                using (var file = new StreamWriter(_appTempLogFile))
                    foreach (var line in crashLog)
                    {
                        file.WriteLine(line);
                    }
            }
            catch (Exception ex)
            {
                Log("Error saving temp log: ", ex);
                return;
            }

            Log("A crash was detected from Gnomoria. Sending crash summary to dev team");

            try
            {
                var headers = new WebHeaderCollection();
                headers.Add("Content-Type", "application/x-www-form-urlencoded");
                headers.Add("GE-computerName", Environment.MachineName);
                headers.Add("GE-computerHash", Settings.Default.hash);

                using (var client = new WebClient())
                {
                    client.Headers = headers;
                    client.UploadFile(uri, _appTempLogFile);
                }
            }
            catch (Exception ex)
            {
                Log("Error uploading log: ", ex);
            }

            try
            {
                File.Delete(_appTempLogFile);
            }
            catch (Exception ex)
            {
                Log("Error deleting temp log: ", ex);
                return;
            }

            Log("Full Crash Log has been sent to dev team. Thank you!");
        }

        private void ApiAddComputer()
        {
            if (!string.IsNullOrEmpty(Settings.Default.hash))
                return;

            var uri = new Uri("http://ge.gnomoriawiki.com/api/computer/add");

            const string queryString = "SELECT SerialNumber FROM Win32_OperatingSystem";

            string productId = (from ManagementObject managementObject in new ManagementObjectSearcher(queryString).Get()
                                from PropertyData propertyData in managementObject.Properties
                                where propertyData.Name == "SerialNumber"
                                select (string)propertyData.Value).FirstOrDefault();

            if (string.IsNullOrEmpty(productId))
            {
                Console.WriteLine(@"Error getting PID");
                return;
            }

            string param = "name=" + Environment.MachineName + "&pid=" + productId;

            var headers = new WebHeaderCollection
                              {
                                  {"GE-computerName", Environment.MachineName},
                                  {"GE-computerHash", Settings.Default.hash},
                                  {"Content-Type", "application/x-www-form-urlencoded"}
                              };

            string result;
            using (var client = new WebClient())
            {
                client.Headers = headers;
                result = client.UploadString(uri, param);
            }

            if (result.Contains("computerHash"))
            {
                var deserialized = JsonConvert.DeserializeObject(result) as JObject;

                JToken temp;

                deserialized.TryGetValue("computerHash", out temp);

                Settings.Default.hash = temp.Value<string>();
                Settings.Default.Save();
            }
            else
            {
                Console.WriteLine(@"Error");
            }
        }

        private void ApiCall(string url, string param, string action)
        {
            
            
            //If $params Then
    //    If $action = "POST" Then $obj.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
    //    $obj.send($params)
    //    debug(" * Params: " & $params)
    //Else
    //    $obj.send()
    //EndIf
    //Local $sData
    //If $obj.Status = 200 Then
    //    $sData = $obj.responsetext
    //    debug("Returned Data: " & $sData)
    //    return $sData
    //Else
    //    warn("Could not connect to " & $url & " with action " & $action & ". Response Status: " & $obj.Status)
    //    SetError(1,$obj.Status)
    //    Return 0
    //EndIf
        }

        private List<string> ReadCrashLog()
        {
            var logFile = new List<string>();

            Console.WriteLine(@"Opening up crash log: " + _gnomoriaLogFile);

            if (!File.Exists(_gnomoriaLogFile))
                return null;

            try
            {
                logFile = File.ReadAllLines(_gnomoriaLogFile).ToList();

                if (logFile.Count < 1)
                {
                    Console.WriteLine(@"Log file empty: " + _gnomoriaLogFile);
                    return null;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(@"Error opening crash log: " + _gnomoriaLogFile + @" " + ex.Message);
            }

            return logFile;
        }

        private void BtnTestSendLogClick(object sender, EventArgs e)
        {
            SendCrashLog();
        }
    }

    public class ThreadInfo
    {
        public int BackupNum;
        public decimal BackupInterval;

        public ThreadInfo(int backupNum, decimal backupInterval)
        {
            BackupNum = backupNum;
            BackupInterval = backupInterval;
        }
    }
}
